#include "SyncRedisClient.h"
#include<iostream>
#include<sstream>
#include<stdlib.h>
using namespace std;


RedisClient::RedisClient(string _ip,int _port)
:m_ip(_ip),
m_port(_port),
m_connectTimeout(1),
m_pContext(NULL),
m_bConnected(false),
m_curIpPortIdx(0),
m_lastAliveValue(-1),
m_lastErrorMsg(),
m_timeout(0)
{       
    addBackupIpPort(_ip, _port);
}

RedisClient::~RedisClient()
{
    if(m_pContext != NULL)
    {
        redisFree(m_pContext);
    }
}

void RedisClient::addBackupIpPort(string ip, int port)
{
    m_ipPorts.push_back(make_pair(ip,port)); 
}

void RedisClient::clearIpPort(string ip, int port)
{
    m_ipPorts.clear(); 	
    m_ipPorts.push_back(make_pair(ip, port));
    m_ip   = ip;
    m_port = port;
}

bool RedisClient::connect(string ip, int port)
{
    if( m_pContext != NULL)
    {
        redisFree(m_pContext);
        m_pContext = NULL;
    }

    //default time out 2 seconds
    timeval timeout;
    timeout.tv_sec  = m_connectTimeout;
    timeout.tv_usec = 0;
    m_pContext = redisConnectWithTimeout(ip.c_str(), port,timeout);
    if(m_pContext == NULL)
    {
        return false;
    }

    if( m_pContext->err == 0 )
    {
        m_bConnected = true;
    }
    else
    {
        m_bConnected = false;
    }

    // set r/w timeout
    if( m_bConnected && (m_timeout > 0) )
    {
        setTimeout(m_timeout);
    }
    return m_bConnected;
}

bool RedisClient::switchRedis()
{
    m_curIpPortIdx++; 
    size_t idx = m_curIpPortIdx % m_ipPorts.size();
    pair<string,int> ipPort = *(m_ipPorts.begin()+idx);
    m_ip   = ipPort.first;
    m_port = ipPort.second;
    return connect(m_ip, m_port);
}

bool RedisClient::isConnected()
{
    return m_bConnected; 
}

string RedisClient::getLastError()
{
    return m_lastErrorMsg;
}

struct redisReply* RedisClient::redisCommandWithArgv(vector<string> &cmds)
{
    if(isConnected() == false)
        connect(m_ip, m_port);
       
    //if still not connected switch to backup Ip Port once
    if(isConnected() == false)
        switchRedis(); 

    redisReply  *reply = NULL;
    
    if(isConnected() == false)
    {
        collectLastError(reply);
        return reply;
    }

    // convert arguments
    vector<const char *> argv(cmds.size());
    vector<size_t>       argvlen(cmds.size());
    int j = 0;
    for (vector<string>::const_iterator it = cmds.begin(); it != cmds.end(); ++it,++j)
    {
        argv[j]    = it->c_str();
        argvlen[j] = it->size();
    }

    reply = (redisReply*)redisCommandArgv(m_pContext, argv.size(), &(argv[0]), &(argvlen[0]));
    collectLastError(reply);

    //because it is synchronize tcp. if tcp is closed by other side(redis server)
    //this could only be detected when do redisvCommand.
    //so if previous command execution failed, should switch redis and try again
    if(isConnected()==false)
    {
        switchRedis(); 
        //if still not connected.
        //have to return error.
        if(isConnected() == false)
        {
            collectLastError(reply);
            return reply;
        }
        //good. connected. execute command.
        reply = (redisReply*)redisCommandArgv(m_pContext, argv.size(), &(argv[0]), &(argvlen[0]));
        collectLastError(reply);
    }
    
    return reply;
}

redisReply* RedisClient::redisCommand(const char *format, ...)
{
    if(isConnected() == false)
        connect(m_ip, m_port);
       
    //if still not connected switch to backup Ip Port once
    if(isConnected() == false)
        switchRedis(); 

    redisReply  *reply = NULL;
    
    if(isConnected() == false)
    {
        collectLastError(reply);
        return reply;
    }
    va_list ap;
    va_start(ap,format);
    reply = (redisReply*)redisvCommand(m_pContext,format,ap);
    collectLastError(reply);
    va_end(ap);


    //because it is synchronize tcp. if tcp is closed by other side(redis server)
    //this could only be detected when do redisvCommand.
    //so if previous command execution failed, should switch redis and try again
    if(isConnected()==false)
    {
        switchRedis(); 
        //if still not connected.
        //have to return error.
        if(isConnected() == false)
        {
            collectLastError(reply);
            return reply;
        }
        //good. connected. execute command.
        va_list ap2;
        va_start(ap2,format);
        reply = (redisReply*)redisvCommand(m_pContext,format,ap2);
        collectLastError(reply);
        va_end(ap2);
    }
    
    return reply;
    
}



bool RedisClient::checkAlive()
{
    redisReply *reply = redisCommand("get salive");
    if(reply == NULL)
        return switchRedis();

    if( (reply->type == REDIS_REPLY_ERROR) || (reply->type == REDIS_REPLY_NIL) )
        return switchRedis();

    //if salive is empty. This redis definitely is not good.
    if(reply->len == 0)
        return switchRedis();

    int curAliveValue = atoi(reply->str);
    
    if(curAliveValue == m_lastAliveValue)
        return switchRedis();
    
    m_lastAliveValue = curAliveValue;
    
    return true;
}


void RedisClient::split(string& s, string& delim,std::vector<string>* ret)
{
    size_t last = 0;
    size_t index=s.find_first_of(delim,last);
    while (index!=std::string::npos)
    {
        ret->push_back(s.substr(last,index-last));
        last=index+1;
        index=s.find_first_of(delim,last);
    }
    if (index-last>0)
    {
        ret->push_back(s.substr(last,index-last));
    }
}


void RedisClient::freeReply(redisReply *reply)
{
    if(reply != NULL && m_pContext != NULL)
    {
        freeReplyObject(reply);
    } 
}

bool RedisClient::setTimeout(int ms)
{
    // save if disconn and set if conn
    m_timeout = ms;
    if(m_pContext == NULL) return false;

    struct timeval tv;
    tv.tv_sec  = ms/1000;
    tv.tv_usec = (ms%1000) * 1000; 
    int ret = redisSetTimeout(m_pContext, tv);
    return (ret==REDIS_OK) ? true:false;
}

template<class T>
string RedisClient::toString(T input)
{
    ostringstream oss;
    oss<< input;
    return oss.str();
}


void RedisClient::collectLastError(redisReply *reply)
{
    m_lastErrorMsg="";
    if( m_pContext == NULL )
    {
        m_lastErrorMsg="error - redis not connected. ip:"+m_ip+" port:"+toString(m_port);
        return;
    }

    if(m_pContext->err != 0)
    {
        m_lastErrorMsg = "ERR TCP: "+ toString(m_pContext->err) + " " + m_pContext->errstr + " ip: " + m_ip + " port: " + toString(m_port);
        //mark this connection down;
        m_bConnected = false;
    }
    else
    {
        if(reply != NULL &&reply->type == REDIS_REPLY_ERROR)
        {
            m_lastErrorMsg = reply->str;
        }
    }
}


